<?php
/**
 * Enviroment
 *
 * Framework Enviroment
 *
 * @category	General
 * @package		dayscript
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @version		$Revision: 0 $
 * @filesource
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-26 $
 */

namespace dayscript;

/**
 * Singleton
 *
 * @category	General
 * @package		dayscript
 * @subpackage
 * @author		Nelson Daza <ndaza@dayscript.com>
 * @copyright	2009 Dayscript Ltda.
 * @license
 * @version		1.0
 * @link		http://www.dayscript.com
 * @link		{docsLink}
 * @uses		\Exception, \InvalidArgumentException, \dayscript\util\Properties, \dayscript\Map, \dayscript\config\Config \dayscript\security\CleanerGlobalData
 * @since		1.0
 * @modifiedby	$LastChangedBy: Nelson Daza $
 * @modified	$Date: 2009-08-26 $
 */
class Enviroment	{
	/**
	 * Singleton instance
	 *
	 * @var Enviroment $instance
	 * @access protected
	 */
	private static $instance = null;
	/**
	 * Actual enviroment index
	 *
	 * @var string $envIndex
	 * @access protected
	 */
	private $envIndex = null;
	/**
	 * Enviroment config values
	 *
	 * @var \dayscript\Map $envConfig
	 * @access protected
	 */
	protected $envConfig = null;
	/**
	 * Enviroment default values
	 *
	 * @var \dayscript\Map $envDefaults
	 * @access protected
	 */
	protected $envDefaults = null;
	/**
	 * Enviroment global data
	 *
	 * @var array
	 * @access private
	 */
	private $data = null;
	
	/**
	 * Enviroment cleaned global data
	 *
	 * @var array
	 * @access private
	 */
	private $dataCleaned = null;
	/**
	 * Enviroment constructor
	 *
	 * @return Enviroment
	 */
	public function __construct( )	{
		
	}
	/**
	 * Returns global Enviroment Instance
	 *
	 * @return Enviroment
	 */
	public static function getInstance( )	{
		if( self::$instance === null )	{
			self::$instance = new self( );
		}
		return self::$instance;
	}
	/**
	 * __call Magic method
	 *
	 * Convenient method if you have a static instance, the __get magic method and a public getInstance method
	 *
	 * @param string $name method's name in lowercase
	 * @param array $arguments If set, its index [0] will be taked as the index into the selected member's name, its [1] will be the default value and its [2] will be a xss cleaner flag, to clean or not the specified data
	 * @return mixed $data This will return a 'Properties' object
	 */
	public function __call( $index, $arguments = null )	{
		$value = ( $arguments && isset( $arguments[0] ) ? $arguments[0] : null );
		$default = ( $arguments && isset( $arguments[1] ) ? $arguments[1] : null );
		$xssClean = ( $arguments && isset( $arguments[2] ) ? $arguments[2] : null );
		
		if ( \array_key_exists( $index, $this->data ) )	{
			if( $value !== null ){
				if( $value !== true ) {
					if( $this->data[$index] instanceOf \dayscript\Map ) {
						if( $xssClean !== null && $xssClean === true ){
							$cleaner = \dayscript\security\CleanerGlobalData::getInstance();
							$tmp = $this->data[$index]->get( $value, $default );
							
							// Check if value wasn't already cleaned
							if( $tmp != $default && (!\array_key_exists( $index, $this->dataCleaned ) || $this->dataCleaned[$index]->get( $value ) === null || $this->dataCleaned[$index]->get( $value ) !== $tmp )){
								$tmp = $cleaner->xssClean($tmp);
								$this->data[$index]->put( $value, $tmp );
								
								if( !\array_key_exists( $index, $this->dataCleaned ) )
									$this->dataCleaned[$index] = new \dayscript\Map();
								$this->dataCleaned[$index]->put( $value, $tmp );
							}
						}
						return $this->data[$index]->get( $value, $default );
					}
					else
						return $default;
				}
				else {
					$cleaner = \dayscript\security\CleanerGlobalData::getInstance();
					$tmp = $cleaner->xssClean($this->data[$index]->toArray());
					$this->data[$index]->putAll($tmp);
				}
			}
			return $this->data[$index];
		}
		else if ( $index == 'keys' )
			return $this->keys( );
		
		return $default;
	}
	/**
	 * __callStatic Magic method
	 *
	 * Convenient method if you have a static instance and the __get magic method
	 *
	 * @param string $name member's name in UPPERCASE with 'get' prefix
	 * @param array $arguments If set, its 0 index will be taked as the index into the selected member's name and its [1] will be the default value
	 * @return mixed $indexData This will return a readonly 'Properties' object
	 */
	public static function __callStatic( $name, $arguments = null )	{
		$staticInstance = self::$instance;
		return $staticInstance->__call( $name, $arguments );
	}
	/**
	 * Enviroment reload Config files
	 *
	 * @param mixed $config string filePath, \dayscript\config\Config, \dayscript\config\Map, array
	 * @param $defaultValues \dayscript\Map
	 * @see dayscript\config\ApplicationDefaults
	 */
	public function reloadConfig( $config, \dayscript\Map $defaultValues )	{
		if( \is_string( $config ) )
			$this->loadConfigFile( $config, $defaultValues );
		else if( $config instanceOf \dayscript\config\Config )
			$this->loadConfig( $config, $defaultValues );
		else if( \is_array( $config ) || $config instanceOf \dayscript\Map )	{
			$last = \end( $config );
			if( \is_string( $last ) && \file_exists( $last ) )
				$this->loadConfigFile( $config, $defaultValues );
			else
				$this->loadConfigValues( $config, $defaultValues );
		}
		else
			throw new \InvalidArgumentException( 'Wrong Enviroment\'s config element.' );
	}
	/**
	 * Enviroment constructor
	 *
	 * @param mixed $config string filePath, \dayscript\config\Config, \dayscript\config\Map, array
	 * @param $defaultValues \dayscript\Map
	 * @see dayscript\config\ApplicationDefaults
	 */
	public function init( $config, \dayscript\Map $defaultValues )	{
		
		if( $this->data != null )
			throw new \dayscript\exception\Exception( 'Trying to load an already loaded enviroment\'s configuration.' );
		
		$this->data = array( );
		$this->dataCleaned = array( );
		if( \is_string( $config ) )
			$this->loadConfigFile( $config, $defaultValues );
		else if( $config instanceOf \dayscript\config\Config )
			$this->loadConfig( $config, $defaultValues );
		else if( \is_array( $config ) || $config instanceOf \dayscript\Map )	{
			$last = \end( $config );
			if( \is_string( $last ) && \file_exists( $last ) )
				$this->loadConfigFile( $config, $defaultValues );
			else
				$this->loadConfigValues( $config, $defaultValues );
		}
		else
			$this->envConfig = new \dayscript\Map( );
			//throw new \InvalidArgumentException( 'Wrong Enviroment\'s config element.' );
		
		if( $this == self::$instance )	{
			\dayscript\log\LogManager::log( 'ENVIROMENT Global Creation.', \dayscript\log\LogManager::LOG_USER_INFO );
			
			$this->setPHPValues( );
			
			// SuperGlobals Auto generation fix
			$_REQUEST;
			$_SERVER;
			$_ENV;

			unset( $GLOBALS['HTTP_POST_FILES'] );
			foreach( $GLOBALS as $key => $value )	{
				if( $key != 'GLOBALS' /*&& $key != '_COOKIE'*/ )	{
					$this->data[\strtolower( \trim( $key, '_' ) )] = ( \is_array( $value ) ? new \dayscript\Map( $GLOBALS[$key] ) : $value );
					unset( $GLOBALS[$key] );
					unset( $GLOBALS['HTTP' . $key . '_VARS'] );
				}
			}
			$GLOBALS = new \dayscript\util\Properties( null, true );
		}
	}
	/**
	 * Registers its shutdown function
	 */
	public function registerShutdown( )	{
		\register_shutdown_function( array( $this, 'shutdownHandler' ) );
	}
	
	private function keys( )	{
		return \array_keys( $this->data );
	}
	/**
	 * Sets enviroment's values
	 *
	 * @param \dayscript\Map $configValues Custom values
	 * @param \dayscript\Map $defaultValues Default values
	 * @see dayscript\config\ApplicationDefaults
	 */
	protected function loadValues( \dayscript\Map $configValues, \dayscript\Map $defaultValues )	{
		$values = new \dayscript\Map( $defaultValues );
		$values['ENVIROMENT'] = $configValues->get( 'ENVIROMENT', 'production' );
		
		if( !$configValues[$values['ENVIROMENT']] )
			$values[$values['ENVIROMENT']] = new \dayscript\Map( $defaultValues );
		else
			$values->putAll( $configValues[$values['ENVIROMENT']] );
		
		$this->envIndex = $values['ENVIROMENT'];
		$this->envConfig = new \dayscript\Map( $values );
		$this->envDefaults = new \dayscript\Map( $values );
		
		$this->data['config'] = $this->envConfig;
		$this->data['default'] = $this->envDefaults;
		
		$this->shapeValues( );
		
		foreach( $this->envConfig as $key => $value )	{
			if( !\array_key_exists( $key, $this->data ) && ( \is_numeric( $value ) || \is_string( $value ) ) )
				$this->data[\strtolower($key)] = $this->envConfig[$key];
		}
		
	}
	/**
	 * Loads custom values from a server file
	 *
	 * @param string $configFile Path to config file
	 * @param \dayscript\Map $defaultValues Default values
	 * @see dayscript\config\ApplicationDefaults
	 */
	protected function loadConfigFile( $configFiles, \dayscript\Map $defaultValues )	{
		$config = new \dayscript\config\Config( );
		$configValues = $config->loadConfig( $configFiles, $defaultValues );
		$this->loadValues( $configValues, $defaultValues );
	}
	/**
	 * Loads custom values from a \dayscript\config\Config object
	 *
	 * @param \dayscript\config\Config $configObj Custom values
	 * @param \dayscript\Map $defaultValues Default values
	 * @see dayscript\config\ApplicationDefaults
	 */
	protected function loadConfig( \dayscript\config\Config $configObj, \dayscript\Map $defaultValues )	{
		$configValues = $configObj->getValues( );
		$this->loadValues( $configValues, $defaultValues );
	}
	/**
	 * Loads custom values from a \dayscript\Map object
	 *
	 * @param \dayscript\Map $configValues Custom values
	 * @param \dayscript\Map $defaultValues Default values
	 * @see dayscript\config\ApplicationDefaults
	 */
	protected function loadConfigValues( \dayscript\Map $configValues, \dayscript\Map $defaultValues )	{
		$configValues = new \dayscript\Map( $configValues );
		$this->loadValues( $configValues, $defaultValues );
	}
	/**
	 * Set all php values found in config
	 */
	protected function setPHPValues( )	{
		
		$php = $this->envConfig->get( 'PHP', new \dayscript\Map( ) );
		$error_repoting = $php->get( 'error_reporting', 0 );
		
		$log = $this->envConfig->get( 'LOG', new \dayscript\Map( ) );
		$threshold = $log->get( 'PHP_THRESHOLD', 0 );
		
		if( \is_string( $threshold ) )
			$threshold = \bindec( $threshold );
		
		$php['error_reporting'] = $error_repoting | $threshold;
		
		foreach( $php as $key => $value )
			\ini_set( $key, $value );
	}
	/**
	 * Revert all php values found in config
	 */
	protected function restorePHPValues( )	{
		$php = $this->envConfig->get( 'PHP', array( ) );
		foreach( $php as $key => $value )
			\ini_restore( $key );
	}
	/**
	 * Validate actual values
	 */
	protected function shapeValues( )	{
		if( $this->envConfig->get( 'HOST', 'AUTO' ) == 'AUTO' )
			$this->envConfig['HOST'] = ( $this == self::$instance ? ( isset( $_SERVER['SERVER_NAME'] ) ? $_SERVER['SERVER_NAME'] : '' ) : self::$instance->get( 'HOST' ) );
		if( $this->envConfig->get( 'PROTOCOL', 'AUTO' ) == 'AUTO' )
			$this->envConfig['PROTOCOL'] = ( $this == self::$instance ? ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? \strtolower( \preg_replace( '/[^a-z]/i', '', $_SERVER['SERVER_PROTOCOL'] ) ) : '' ) : self::$instance->get( 'PROTOCOL' ) );
		if( $this->envConfig->get( 'PORT', '' ) == '' )	{
			$this->envConfig['PORT'] = ( $this == self::$instance ? ( isset( $_SERVER['SERVER_PORT'] ) ? $_SERVER['SERVER_PORT'] : '' ) : self::$instance->get( 'PORT' ) );
			if( $this->envConfig['PORT'] == '80' )
				$this->envConfig['PORT'] = '';
		}
		if( $this->envConfig->get( 'PATH', '' ) == '' )
			$this->envConfig['PATH'] = ( ( $this == self::$instance && isset ($_SERVER['SCRIPT_NAME'] ) ) ? \dirname( $_SERVER['SCRIPT_NAME'] ) . '/' : self::$instance->get( 'PATH' ) );
		
		$this->envConfig['SCRIPT'] = ( ( $this == self::$instance && isset ($_SERVER['SCRIPT_NAME'] ) ) ? \basename( $_SERVER['SCRIPT_NAME'] ) : self::$instance->get( 'SCRIPT' ) );
		$this->envConfig['HOST_URL'] = $this->envConfig['PROTOCOL'] . '://' . $this->envConfig['HOST'] . ( $this->envConfig['PORT'] ? ':' . $this->envConfig['PORT'] : '' ) . $this->envConfig['PATH'] . $this->envConfig['SCRIPT'];
		$this->envConfig['QUERY_STRING'] = ( $this == self::$instance ? ( isset( $_SERVER['QUERY_STRING'] ) ? $_SERVER['QUERY_STRING'] : '' ) : ( isset( self::$instance->QUERY_STRING ) ? self::$instance->QUERY_STRING : null ) );
		
		if( !$this->envConfig['DIR'] )	{
			$stack = ErrorManager::getBacktrace( );
			$stack = \array_pop( $stack );
			$this->envConfig['DIR'] = ( \dirname( $stack['file'] ) . \DIRECTORY_SEPARATOR );
		}
	}
	/**
	 * Global script shutdown Handler.
	 * This will restore every global variables and data but PHP values.
	 * @param mixed $params
	 */
	public static function shutdownHandler( $params = null )	{
		\dayscript\log\LogManager::log( 'SHUTDOWN Enviroment (2).', \dayscript\log\LogManager::LOG_USER_INFO );
		$GLOBALS = array( );
		$GLOBALS['GLOBALS'] = &$GLOBALS;
		\flush( );
	}
}